/*
 * easy-autocomplete
 * jQuery plugin for autocompletion
 * 
 * @author Łukasz Pawełczak (http://github.com/pawelczak)
 * @version 1.3.5
 * Copyright  License: 
 */

/*
 * EasyAutocomplete - Configuration 
 */
define([
  'jimu/loaderplugins/jquery-loader! ./libs/jquery/jquery-1-11-1.js'
],
    
(function ($) {
  
  var EasyAutocomplete = (function(scope){

    scope.Configuration = function Configuration(options) {
      var defaults = {
        data: "list-required",
        url: "list-required",
        dataType: "json",

        listLocation: function(data) {
          return data;
        },

        xmlElementName: "",

        getValue: function(element) {
          return element;
        },

        autocompleteOff: true,

        placeholder: false,

        ajaxCallback: function() {},

        matchResponseProperty: false,

        list: {
          sort: {
            enabled: false,
            method: function(a, b) {
              a = defaults.getValue(a);
              b = defaults.getValue(b);
              if (a < b) {
                return -1;
              }
              if (a > b) {
                return 1;
              }
              return 0;
            }
          },

          maxNumberOfElements: 6,

          hideOnEmptyPhrase: true,

          match: {
            enabled: false,
            caseSensitive: false,
            method: function(element, phrase) {

              if (element.search(phrase) > -1) {
                return true;
              } else {
                return false;
              }
            }
          },

          showAnimation: {
            type: "normal", //normal|slide|fade
            time: 400,
            callback: function() {}
          },

          hideAnimation: {
            type: "normal",
            time: 400,
            callback: function() {}
          },

          /* Events */
          onClickEvent: function() {},
          onSelectItemEvent: function() {},
          onLoadEvent: function() {},
          onChooseEvent: function() {},
          onKeyEnterEvent: function() {},
          onMouseOverEvent: function() {},
          onMouseOutEvent: function() {},	
          onShowListEvent: function() {},
          onHideListEvent: function() {}
        },

        highlightPhrase: true,

        theme: "",

        cssClasses: "",

        minCharNumber: 0,

        requestDelay: 0,

        adjustWidth: true,

        ajaxSettings: {},

        preparePostData: function(data) {return data;},

        loggerEnabled: true,

        template: "",

        categoriesAssigned: false,

        categories: [{
          maxNumberOfElements: 4
        }]

      };
      
      var externalObjects = ["ajaxSettings", "template"];

      this.get = function(propertyName) {
        return defaults[propertyName];
      };

      this.equals = function(name, value) {
        if (isAssigned(name)) {
          if (defaults[name] === value) {
            return true;
          }
        } 
        
        return false;
      };

      this.checkDataUrlProperties = function() {
        if (defaults.url === "list-required" && defaults.data === "list-required") {
          return false;
        }
        return true;
      };
      this.checkRequiredProperties = function() {
        for (var propertyName in defaults) {
          if (defaults[propertyName] === "required") {
            return false;
          }
        }
        return true;
      };

      this.printPropertiesThatDoesntExist = function(consol, optionsToCheck) {
        printPropertiesThatDoesntExist(consol, optionsToCheck);
      };


      prepareDefaults();

      mergeOptions();

      if (defaults.loggerEnabled === true) {
        printPropertiesThatDoesntExist(console, options);	
      }

      addAjaxSettings();

      processAfterMerge();
      function prepareDefaults() {

        if (options.dataType === "xml") {
          
          if (!options.getValue) {

            options.getValue = function(element) {
              return $(element).text();
            };
          }

          
          if (!options.list) {

            options.list = {};
          } 

          if (!options.list.sort) {
            options.list.sort = {};
          }


          options.list.sort.method = function(a, b) {
            a = options.getValue(a);
            b = options.getValue(b);
            if (a < b) {
              return -1;
            }
            if (a > b) {
              return 1;
            }
            return 0;
          };

          if (!options.list.match) {
            options.list.match = {};
          }

          options.list.match.method = function(element, phrase) {

            if (element.search(phrase) > -1) {
              return true;
            } else {
              return false;
            }
          };

        }
        if (options.categories !== undefined && options.categories instanceof Array) {

          var categories = [];

          for (var i = 0, length = options.categories.length; i < length; i += 1) { 

            var category = options.categories[i];

            for (var property in defaults.categories[0]) {

              if (category[property] === undefined) {
                category[property] = defaults.categories[0][property];
              }
            }

            categories.push(category);
          }

          options.categories = categories;
        }
      }

      function mergeOptions() {

        defaults = mergeObjects(defaults, options);

        function mergeObjects(source, target) {
          var mergedObject = source || {};

          for (var propertyName in source) {
            if (target[propertyName] !== undefined && target[propertyName] !== null) {

              if (typeof target[propertyName] !== "object" || 
                  target[propertyName] instanceof Array) {
                mergedObject[propertyName] = target[propertyName];
              } else {
                mergeObjects(source[propertyName], target[propertyName]);
              }
            }
          }
        
          /* If data is an object */
          if (target.data !== undefined && target.data !== null && 
              typeof target.data === "object") {
            mergedObject.data = target.data;
          }

          return mergedObject;
        }
      }	


      function processAfterMerge() {
        var defaultUrl;
        if (defaults.url !== "list-required" && typeof defaults.url !== "function") {
          defaultUrl = defaults.url;
          defaults.url = function() {
            return defaultUrl;
          };
        }

        if (defaults.ajaxSettings.url !== undefined && 
            typeof defaults.ajaxSettings.url !== "function") {
          defaultUrl = defaults.ajaxSettings.url;
          defaults.ajaxSettings.url = function() {
            return defaultUrl;
          };
        }

        if (typeof defaults.listLocation === "string") {
          var defaultlistLocation = defaults.listLocation;

          if (defaults.dataType.toUpperCase() === "XML") {
            defaults.listLocation = function(data) {
              return $(data).find(defaultlistLocation);
            };
          } else {
            defaults.listLocation = function(data) {
              return data[defaultlistLocation];
            };	
          }
        }

        if (typeof defaults.getValue === "string") {
          var defaultsGetValue = defaults.getValue;
          defaults.getValue = function(element) {
            return element[defaultsGetValue];
          };
        }

        if (options.categories !== undefined) {
          defaults.categoriesAssigned = true;
        }

      }

      function addAjaxSettings() {

        if (options.ajaxSettings !== undefined && typeof options.ajaxSettings === "object") {
          defaults.ajaxSettings = options.ajaxSettings;
        } else {
          defaults.ajaxSettings = {};	
        }
        
      }

      function isAssigned(name) {
        if (defaults[name] !== undefined && defaults[name] !== null) {
          return true;
        } else {
          return false;
        }
      }
      function printPropertiesThatDoesntExist(consol, optionsToCheck) {
        
        checkPropertiesIfExist(defaults, optionsToCheck);

        function checkPropertiesIfExist(source, target) {
          for(var property in target) {
            if (source[property] === undefined) {
              consol.log("Property '" + property + 
                "' does not exist in EasyAutocomplete options API.");		
            }

            if (typeof source[property] === "object" && 
              $.inArray(property, externalObjects) === -1) {
              checkPropertiesIfExist(source[property], target[property]);
            }
          }	
        }
      }
    };

    return scope;

  })(EasyAutocomplete || {});


  /*
   * EasyAutocomplete - Logger 
   */
  EasyAutocomplete = (function(scope){
    
    scope.Logger = function Logger() {

      this.error = function(message) {
      };

      this.warning = function(message) {
      };
    };

    return scope;

  })(EasyAutocomplete || {});
    

  /*
   * EasyAutocomplete - Constans
   */
  EasyAutocomplete = (function(scope){	
    
    scope.Constans = function Constans() {
      var constants = {
        CONTAINER_CLASS: "easy-autocomplete-container",
        CONTAINER_ID: "eac-container-",

        WRAPPER_CSS_CLASS: "easy-autocomplete"
      };

      this.getValue = function(propertyName) {
        return constants[propertyName];
      };

    };

    return scope;

  })(EasyAutocomplete || {});

  /*
   * EasyAutocomplete - ListBuilderService 
   *
   * @author Łukasz Pawełczak 
   *
   */
  EasyAutocomplete = (function(scope) {

    scope.ListBuilderService = function ListBuilderService(configuration, proccessResponseData) {


      this.init = function(data) {
        var listBuilder = [],
          builder = {};

        builder.data = configuration.get("listLocation")(data);
        builder.getValue = configuration.get("getValue");
        builder.maxListSize = configuration.get("list").maxNumberOfElements;

          
        listBuilder.push(builder);

        return listBuilder;
      };

      this.updateCategories = function(listBuilder, data) {
        
        if (configuration.get("categoriesAssigned")) {

          listBuilder = [];

          for(var i = 0; i < configuration.get("categories").length; i += 1) {

            var builder = convertToListBuilder(configuration.get("categories")[i], data);

            listBuilder.push(builder);
          }

        } 

        return listBuilder;
      };

      this.convertXml = function(listBuilder) {
        if(configuration.get("dataType").toUpperCase() === "XML") {

          for(var i = 0; i < listBuilder.length; i += 1) {
            listBuilder[i].data = convertXmlToList(listBuilder[i]);
          }
        }

        return listBuilder;
      };

      this.processData = function(listBuilder, inputPhrase) {

        for(var i = 0, length = listBuilder.length; i < length; i+=1) {
          listBuilder[i].data = proccessResponseData(configuration, listBuilder[i], inputPhrase);
        }

        return listBuilder;
      };

      this.checkIfDataExists = function(listBuilders) {

        for(var i = 0, length = listBuilders.length; i < length; i += 1) {

          if (listBuilders[i].data !== undefined && listBuilders[i].data instanceof Array) {
            if (listBuilders[i].data.length > 0) {
              return true;
            }
          } 
        }

        return false;
      };


      function convertToListBuilder(category, data) {

        var builder = {};

        if(configuration.get("dataType").toUpperCase() === "XML") {

          builder = convertXmlToListBuilder();
        } else {

          builder = convertDataToListBuilder();
        }
        

        if (category.header !== undefined) {
          builder.header = category.header;
        }

        if (category.maxNumberOfElements !== undefined) {
          builder.maxNumberOfElements = category.maxNumberOfElements;
        }

        if (configuration.get("list").maxNumberOfElements !== undefined) {

          builder.maxListSize = configuration.get("list").maxNumberOfElements;
        }

        if (category.getValue !== undefined) {

          if (typeof category.getValue === "string") {
            var defaultsGetValue = category.getValue;
            builder.getValue = function(element) {
              return element[defaultsGetValue];
            };
          } else if (typeof category.getValue === "function") {
            builder.getValue = category.getValue;
          }

        } else {
          builder.getValue = configuration.get("getValue");	
        }
        

        return builder;


        function convertXmlToListBuilder() {

          var builder = {},
            listLocation;

          if (category.xmlElementName !== undefined) {
            builder.xmlElementName = category.xmlElementName;
          }

          if (category.listLocation !== undefined) {

            listLocation = category.listLocation;
          } else if (configuration.get("listLocation") !== undefined) {

            listLocation = configuration.get("listLocation");
          }

          if (listLocation !== undefined) {
            if (typeof listLocation === "string") {
              builder.data = $(data).find(listLocation);
            } else if (typeof listLocation === "function") {

              builder.data = listLocation(data);
            }
          } else {

            builder.data = data;
          }

          return builder;
        }


        function convertDataToListBuilder() {

          var builder = {};

          if (category.listLocation !== undefined) {

            if (typeof category.listLocation === "string") {
              builder.data = data[category.listLocation];
            } else if (typeof category.listLocation === "function") {
              builder.data = category.listLocation(data);
            }
          } else {
            builder.data = data;
          }

          return builder;
        }
      }

      function convertXmlToList(builder) {
        var simpleList = [];

        if (builder.xmlElementName === undefined) {
          builder.xmlElementName = configuration.get("xmlElementName");
        }


        $(builder.data).find(builder.xmlElementName).each(function() {
          simpleList.push(this);
        });

        return simpleList;
      }

    };

    return scope;

  })(EasyAutocomplete || {});


  /*
   * EasyAutocomplete - Data proccess module
   *
   * Process list to display:
   * - sort 
   * - decrease number to specific number
   * - show only matching list
   *
   */
  EasyAutocomplete = (function(scope) {

    scope.proccess = function proccessData(config, listBuilder, phrase) {

      scope.proccess.match = match;

      var list = listBuilder.data,
        inputPhrase = phrase;//TODO REFACTOR

      list = findMatch(list, inputPhrase);
      list = reduceElementsInList(list);
      list = sort(list);

      return list;


      function findMatch(list, phrase) {
        var preparedList = [],
          value = "";

        if (config.get("list").match.enabled) {

          for(var i = 0, length = list.length; i < length; i += 1) {

            value = config.get("getValue")(list[i]);
            
            if (match(value, phrase)) {
              preparedList.push(list[i]);	
            }
            
          }

        } else {
          preparedList = list;
        }

        return preparedList;
      }

      function match(value, phrase) {

        if (!config.get("list").match.caseSensitive) {

          if (typeof value === "string") {
            value = value.toLowerCase();	
          }
          
          phrase = phrase.toLowerCase();
        }
        if (config.get("list").match.method(value, phrase)) {
          return true;
        } else {
          return false;
        }
      }

      function reduceElementsInList(list) {
        if (listBuilder.maxNumberOfElements !== undefined && 
            list.length > listBuilder.maxNumberOfElements) {
          list = list.slice(0, listBuilder.maxNumberOfElements);
        }

        return list;
      }

      function sort(list) {
        if (config.get("list").sort.enabled) {
          list.sort(config.get("list").sort.method);
        }

        return list;
      }
      
    };


    return scope;


  })(EasyAutocomplete || {});


  /*
   * EasyAutocomplete - Template 
   *
   * 
   *
   */
  EasyAutocomplete = (function(scope){

    scope.Template = function Template(options) {


      var genericTemplates = {
        basic: {
          type: "basic",
          method: function(element) { return element; },
          cssClass: ""
        },
        description: {
          type: "description",
          fields: {
            description: "description"
          },
          method: function(element) {	return element + " - description"; },
          cssClass: "eac-description"
        },
        iconLeft: {
          type: "iconLeft",
          fields: {
            icon: ""
          },
          method: function(element) {
            return element;
          },
          cssClass: "eac-icon-left"
        },
        iconRight: {
          type: "iconRight",
          fields: {
            iconSrc: ""
          },
          method: function(element) {
            return element;
          },
          cssClass: "eac-icon-right"
        },
        links: {
          type: "links",
          fields: {
            link: ""
          },
          method: function(element) {
            return element;
          },
          cssClass: ""
        },
        custom: {
          type: "custom",
          method: function() {},
          cssClass: ""
        }
      },



      /*
       * Converts method with {{text}} to function
       */
      convertTemplateToMethod = function(template) {


        var _fields = template.fields,
          buildMethod;

        if (template.type === "description") {

          buildMethod = genericTemplates.description.method; 

          if (typeof _fields.description === "string") {
            buildMethod = function(elementValue, element) {
              return elementValue + " - <span>" + element[_fields.description] + "</span>";
            };					
          } else if (typeof _fields.description === "function") {
            buildMethod = function(elementValue, element) {
              return elementValue + " - <span>" + _fields.description(element) + "</span>";
            };	
          }

          return buildMethod;
        }

        if (template.type === "iconRight") {

          if (typeof _fields.iconSrc === "string") {
            buildMethod = function(elementValue, element) {
              return elementValue + "<img class='eac-icon' src='" + 
                element[_fields.iconSrc] + "' />" ;
            };					
          } else if (typeof _fields.iconSrc === "function") {
            buildMethod = function(elementValue, element) {
              return elementValue + "<img class='eac-icon' src='" + 
                _fields.iconSrc(element) + "' />" ;
            };
          }

          return buildMethod;
        }


        if (template.type === "iconLeft") {

          if (typeof _fields.iconSrc === "string") {
            buildMethod = function(elementValue, element) {
              return "<img class='eac-icon' src='" + 
                element[_fields.iconSrc] + "' />" + elementValue;
            };					
          } else if (typeof _fields.iconSrc === "function") {
            buildMethod = function(elementValue, element) {
              return "<img class='eac-icon' src='" + 
                _fields.iconSrc(element) + "' />" + elementValue;
            };
          }

          return buildMethod;
        }

        if(template.type === "links") {

          if (typeof _fields.link === "string") {
            buildMethod = function(elementValue, element) {
              return "<a href='" + element[_fields.link] + "' >" + elementValue + "</a>";
            };					
          } else if (typeof _fields.link === "function") {
            buildMethod = function(elementValue, element) {
              return "<a href='" + _fields.link(element) + "' >" + elementValue + "</a>";
            };
          }

          return buildMethod;
        }


        if (template.type === "custom") {

          return template.method;
        }

        return genericTemplates.basic.method;

      },


      prepareBuildMethod = function(options) {
        if (!options || !options.type) {

          return genericTemplates.basic.method;
        }

        if (options.type && genericTemplates[options.type]) {

          return convertTemplateToMethod(options);
        } else {

          return genericTemplates.basic.method;
        }

      },

      templateClass = function(options) {
        var emptyStringFunction = function() {return "";};

        if (!options || !options.type) {

          return emptyStringFunction;
        }

        if (options.type && genericTemplates[options.type]) {
          return (function () { 
            var _cssClass = genericTemplates[options.type].cssClass;
            return function() { return _cssClass;};
          })();
        } else {
          return emptyStringFunction;
        }
      };


      this.getTemplateClass = templateClass(options);

      this.build = prepareBuildMethod(options);


    };

    return scope;

  })(EasyAutocomplete || {});


  /*
   * EasyAutocomplete - jQuery plugin for autocompletion
   *
   */
  EasyAutocomplete = (function(scope) {

    
    scope.main = function Core($input, options) {
          
      var consts = new scope.Constans(),
        config = new scope.Configuration(options),
        logger = new scope.Logger(),
        template = new scope.Template(options.template),
        listBuilderService = new scope.ListBuilderService(config, scope.proccess),
        checkParam = config.equals,

        $field = $input, 
        $container = "",
        elementsList = [],
        selectedElement = -1,
        requestDelayTimeoutId;

      scope.consts = consts;

      this.getConstants = function() {
        return consts;
      };

      this.getConfiguration = function() {
        return config;
      };

      this.getContainer = function() {
        return $container;
      };

      this.getSelectedItemIndex = function() {
        return selectedElement;
      };

      this.getItems = function () {
        return elementsList;
      };

      this.getItemData = function(index) {

        if (elementsList.length < index || elementsList[index] === undefined) {
          return -1;
        } else {
          return elementsList[index];
        }
      };

      this.getSelectedItemData = function() {
        return this.getItemData(selectedElement);
      };

      this.build = function() {
        prepareField();
      };

      this.init = function() {
        init();
      };
      function init() {

        if ($field.length === 0) {
          logger.error("Input field doesn't exist.");
          return;
        }

        if (!config.checkDataUrlProperties()) {
          logger.error("One of options variables 'data' or 'url' must be defined.");
          return;
        }

        if (!config.checkRequiredProperties()) {
          logger.error("Will not work without mentioned properties.");
          return;
        }


        prepareField();
        bindEvents();	

      }
      function prepareField() {

          
        if ($field.parent().hasClass(consts.getValue("WRAPPER_CSS_CLASS"))) {
          removeContainer();
          removeWrapper();
        } 
        
        createWrapper();
        createContainer();	

        $container = $("#" + getContainerId());
        if (config.get("placeholder")) {
          $field.attr("placeholder", config.get("placeholder"));
        }


        function createWrapper() {
          var $wrapper = $("<div>"),
            classes = consts.getValue("WRAPPER_CSS_CLASS");

        
          if (config.get("theme") && config.get("theme") !== "") {
            classes += " eac-" + config.get("theme");
          }

          if (config.get("cssClasses") && config.get("cssClasses") !== "") {
            classes += " " + config.get("cssClasses");
          }

          if (template.getTemplateClass() !== "") {
            classes += " " + template.getTemplateClass();
          }
          

          $wrapper
            .addClass(classes);
          $field.wrap($wrapper);


          if (config.get("adjustWidth") === true) {
            adjustWrapperWidth();	
          }
          

        }

        function adjustWrapperWidth() {
          $field.parent().css("width", "100%");				
        }

        function removeWrapper() {
          $field.unwrap();
        }

        function createContainer() {
          var $elements_container = $("<div>").addClass(consts.getValue("CONTAINER_CLASS"));

          $elements_container
              .attr("id", getContainerId())
              .prepend($("<ul>"));


          (function() {

            $elements_container
              /* List show animation */
              .on("show.eac", function() {
                var animationTime, callback;
                switch(config.get("list").showAnimation.type) {

                  case "slide":
                    animationTime = config.get("list").showAnimation.time;
                    callback = config.get("list").showAnimation.callback;

                    $elements_container.find("ul").slideDown(animationTime, callback);
                  break;

                  case "fade":
                    animationTime = config.get("list").showAnimation.time;
                    callback = config.get("list").showAnimation.callback;

                    $elements_container.find("ul").fadeIn(animationTime, callback);
                  break;

                  default:
                    $elements_container.find("ul").show();
                  break;
                }

                config.get("list").onShowListEvent();
                
              })
              /* List hide animation */
              .on("hide.eac", function() {
                var animationTime, callback;
                switch(config.get("list").hideAnimation.type) {

                  case "slide":
                    animationTime = config.get("list").hideAnimation.time;
                    callback = config.get("list").hideAnimation.callback;
                    $elements_container.find("ul").slideUp(animationTime, callback);
                  break;

                  case "fade":
                    animationTime = config.get("list").hideAnimation.time;
                    callback = config.get("list").hideAnimation.callback;
                    $elements_container.find("ul").fadeOut(animationTime, callback);
                  break;

                  default:
                    $elements_container.find("ul").hide();
                  break;
                }

                config.get("list").onHideListEvent();

              })
              .on("selectElement.eac", function() {
                $elements_container.find("ul li").removeClass("selected");
                $elements_container.find("ul li").eq(selectedElement).addClass("selected");

                config.get("list").onSelectItemEvent();
              })
              .on("loadElements.eac", function(event, listBuilders, phrase) {
                event = null;

                var $item = "",
                  $listContainer = $elements_container.find("ul");

                $listContainer
                  .empty()
                  .detach();

                elementsList = [];
                var counter = 0;
                for(var builderIndex = 0, listBuildersLength = listBuilders.length; 
                  builderIndex < listBuildersLength; builderIndex += 1) {

                  var listData = listBuilders[builderIndex].data;

                  if (listData.length === 0) {
                    continue;
                  }

                  if (listBuilders[builderIndex].header !== undefined && 
                    listBuilders[builderIndex].header.length > 0) {
                    $listContainer.append("<div class='eac-category' >" + 
                      listBuilders[builderIndex].header + "</div>");
                  }

                  for(var i = 0, listDataLength = listData.length; i < listDataLength && 
                      counter < listBuilders[builderIndex].maxListSize; i += 1) {
                    $item = $("<li><div class='eac-item'></div></li>");
                    

                    (function() {
                      var j = i,
                        itemCounter = counter,
                        elementsValue = listBuilders[builderIndex].getValue(listData[j]);

                      $item.find(" > div")
                        .on("click", function() {

                          $field.val(elementsValue).trigger("change");

                          selectedElement = itemCounter;
                          selectElement(itemCounter);

                          config.get("list").onClickEvent();
                          config.get("list").onChooseEvent();
                        })
                        .mouseover(function() {

                          selectedElement = itemCounter;
                          selectElement(itemCounter);	

                          config.get("list").onMouseOverEvent();
                        })
                        .mouseout(function() {
                          config.get("list").onMouseOutEvent();
                        })
                        .html(template.build(highlight(elementsValue, phrase), listData[j]));
                    })();

                    $listContainer.append($item);
                    elementsList.push(listData[i]);
                    counter += 1;
                  }
                }

                $elements_container.append($listContainer);

                config.get("list").onLoadEvent();
              });

          })();

          $field.after($elements_container);
        }

        function removeContainer() {
          $field.next("." + consts.getValue("CONTAINER_CLASS")).remove();
        }

        function highlight(string, phrase) {

          if(config.get("highlightPhrase") && phrase !== "") {
            return highlightPhrase(string, phrase);	
          } else {
            return string;
          }
            
        }

        function escapeRegExp(str) {
          return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
        }

        function highlightPhrase(string, phrase) {
          var escapedPhrase = escapeRegExp(phrase);
          return (string + "").replace(new RegExp("(" + escapedPhrase + ")", "gi") , "<b>$1</b>");
        }



      }
      function getContainerId() {
        
        var elementId = $field.attr("id");

        elementId = consts.getValue("CONTAINER_ID") + elementId;

        return elementId;
      }
      function bindEvents() {

        bindAllEvents();
        

        function bindAllEvents() {
          if (checkParam("autocompleteOff", true)) {
            removeAutocomplete();
          }

          bindFocusOut();
          bindKeyup();
          bindKeydown();
          bindKeypress();
          bindFocus();
          bindBlur();
        }

        function bindFocusOut() {
          $field.focusout(function () {

            var fieldValue = $field.val(),
              phrase;

            if (!config.get("list").match.caseSensitive) {
              fieldValue = fieldValue.toLowerCase();
            }

            for (var i = 0, length = elementsList.length; i < length; i += 1) {

              phrase = config.get("getValue")(elementsList[i]);
              if (!config.get("list").match.caseSensitive) {
                phrase = phrase.toLowerCase();
              }

              if (phrase === fieldValue) {
                selectedElement = i;
                selectElement(selectedElement);
                return;
              }
            }
          });
        }

        function bindKeyup() {
          $field
          .off("keyup")
          .keyup(function(event) {

            switch(event.keyCode) {

              case 27:

                hideContainer();
                loseFieldFocus();
              break;

              case 38:

                event.preventDefault();

                if(elementsList.length > 0 && selectedElement > 0) {

                  selectedElement -= 1;

                  $field.val(config.get("getValue")(elementsList[selectedElement]));

                  selectElement(selectedElement);

                }						
              break;

              case 40:

                event.preventDefault();

                if(elementsList.length > 0 && selectedElement < elementsList.length - 1) {

                  selectedElement += 1;

                  $field.val(config.get("getValue")(elementsList[selectedElement]));

                  selectElement(selectedElement);
                  
                }

              break;

              default:

                if (event.keyCode > 40 || event.keyCode === 8) {

                  var inputPhrase = $field.val();

                  if (!(config.get("list").hideOnEmptyPhrase === true && 
                    event.keyCode === 8 && inputPhrase === "")) {

                    if (config.get("requestDelay") > 0) {
                      if (requestDelayTimeoutId !== undefined) {
                        clearTimeout(requestDelayTimeoutId);
                      }

                      requestDelayTimeoutId = setTimeout(function () { loadData(inputPhrase);}, 
                        config.get("requestDelay"));
                    } else {
                      loadData(inputPhrase);
                    }

                  } else {
                    hideContainer();
                  }
                  
                }


              break;
            }
          

            function loadData(inputPhrase) {


              if (inputPhrase.length < config.get("minCharNumber")) {
                return;
              }


              if (config.get("data") !== "list-required") {

                var data = config.get("data");

                var listBuilders = listBuilderService.init(data);

                listBuilders = listBuilderService.updateCategories(listBuilders, data);
                
                listBuilders = listBuilderService.processData(listBuilders, inputPhrase);

                loadElements(listBuilders, inputPhrase);

                if ($field.parent().find("li").length > 0) {
                  showContainer();	
                } else {
                  hideContainer();
                }

              }

              var settings = createAjaxSettings();

              if (settings.url === undefined || settings.url === "") {
                settings.url = config.get("url");
              }

              if (settings.dataType === undefined || settings.dataType === "") {
                settings.dataType = config.get("dataType");
              }


              if (settings.url !== undefined && settings.url !== "list-required") {

                settings.url = settings.url(inputPhrase);

                settings.data = config.get("preparePostData")(settings.data, inputPhrase);

                $.ajax(settings) 
                  .done(function(data) {

                    var listBuilders = listBuilderService.init(data);

                    listBuilders = listBuilderService.updateCategories(listBuilders, data);
                    
                    listBuilders = listBuilderService.convertXml(listBuilders);
                    if (checkInputPhraseMatchResponse(inputPhrase, data)) {

                      listBuilders = listBuilderService.processData(listBuilders, inputPhrase);

                      loadElements(listBuilders, inputPhrase);	
                                          
                    }

                    if (listBuilderService.checkIfDataExists(listBuilders) && 
                      $field.parent().find("li").length > 0) {
                      showContainer();	
                    } else {
                      hideContainer();
                    }

                    config.get("ajaxCallback")();

                  })
                  .fail(function() {
                    logger.warning("Fail to load response data");
                  })
                  .always(function() {

                  });
              }

              

              function createAjaxSettings() {

                var settings = {},
                  ajaxSettings = config.get("ajaxSettings") || {};

                for (var set in ajaxSettings) {
                  settings[set] = ajaxSettings[set];
                }

                return settings;
              }

              function checkInputPhraseMatchResponse(inputPhrase, data) {

                if (config.get("matchResponseProperty") !== false) {
                  if (typeof config.get("matchResponseProperty") === "string") {
                    return (data[config.get("matchResponseProperty")] === inputPhrase);
                  }

                  if (typeof config.get("matchResponseProperty") === "function") {
                    return (config.get("matchResponseProperty")(data) === inputPhrase);
                  }

                  return true;
                } else {
                  return true;
                }

              }

            }


          });
        }

        function bindKeydown() {
          $field
            .on("keydown", function(evt) {
                    evt = evt || window.event;
                    var keyCode = evt.keyCode;
                    if (keyCode === 38) {
                        return false;
                    }
                })
            .keydown(function(event) {

              if (event.keyCode === 13 && selectedElement > -1) {

                $field.val(config.get("getValue")(elementsList[selectedElement]));

                config.get("list").onKeyEnterEvent();
                config.get("list").onChooseEvent();

                selectedElement = -1;
                hideContainer();

                event.preventDefault();
              }
            });
        }

        function bindKeypress() {
          $field
          .off("keypress");
        }

        function bindFocus() {
          $field.focus(function() {

            if ($field.val() !== "" && elementsList.length > 0) {
              
              selectedElement = -1;
              showContainer();	
            }
                    
          });
        }

        function bindBlur() {
          $field.blur(function() {
            setTimeout(function() { 
              
              selectedElement = -1;
              hideContainer();
            }, 250);
          });
        }

        function removeAutocomplete() {
          $field.attr("autocomplete","off");
        }

      }

      function showContainer() {
        $container.trigger("show.eac");
      }

      function hideContainer() {
        $container.trigger("hide.eac");
      }

      function selectElement(index) {
        
        $container.trigger("selectElement.eac", index);
      }

      function loadElements(list, phrase) {
        $container.trigger("loadElements.eac", [list, phrase]);
      }

      function loseFieldFocus() {
        $field.trigger("blur");
      }


    };
    scope.eacHandles = [];

    scope.getHandle = function(id) {
      return scope.eacHandles[id];
    };

    scope.inputHasId = function(input) {

      if($(input).attr("id") !== undefined && $(input).attr("id").length > 0) {
        return true;
      } else {
        return false;
      }

    };

    scope.assignRandomId = function(input) {

      var fieldId = "";

      do {
        fieldId = "eac-" + Math.floor(Math.random() * 10000);		
      } while ($("#" + fieldId).length !== 0);
      
      
      $(input).attr("id", fieldId);
   
    };

    scope.setHandle = function(handle, id) {
      scope.eacHandles[id] = handle;
    };


    return scope;

  })(EasyAutocomplete || {});

  (function($) {

    $.fn.easyAutocomplete = function(options) {

      return this.each(function() {
        var $this = $(this),
          eacHandle = new EasyAutocomplete.main($this, options);

        if (!EasyAutocomplete.inputHasId($this)) {
          EasyAutocomplete.assignRandomId($this);
        }

        eacHandle.init();

        EasyAutocomplete.setHandle(eacHandle, $this.attr("id"));

      });
    };

    $.fn.getSelectedItemIndex = function() {

      var inputId = $(this).attr("id");

      if (inputId !== undefined) {
        return EasyAutocomplete.getHandle(inputId).getSelectedItemIndex();
      }

      return -1;
    };

    $.fn.getItems = function () {

      var inputId = $(this).attr("id");

      if (inputId !== undefined) {
        return EasyAutocomplete.getHandle(inputId).getItems();
      }

      return -1;
    };

    $.fn.getItemData = function(index) {

      var inputId = $(this).attr("id");

      if (inputId !== undefined && index > -1) {
        return EasyAutocomplete.getHandle(inputId).getItemData(index);
      }

      return -1;
    };

    $.fn.getSelectedItemData = function() {

      var inputId = $(this).attr("id");

      if (inputId !== undefined) {
        return EasyAutocomplete.getHandle(inputId).getSelectedItemData();
      }

      return -1;
    };

  })(jQuery);                               
}));